package com.itheima.service.impl;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.itheima.common.DefaultException;
import com.itheima.common.Paging;
import com.itheima.convert.NoticeConvert;
import com.itheima.convert.OrganizationConvert;
import com.itheima.dto.NoticeDto;
import com.itheima.dto.PageNoticeDto;
import com.itheima.dto.UpdateNoticeDto;
import com.itheima.entity.Notice;
import com.itheima.entity.User;
import com.itheima.entity.UserLinkNotice;
import com.itheima.enums.NoticeEnum;
import com.itheima.enums.NoticeStatusEnum;
import com.itheima.enums.OrganizationEnum;
import com.itheima.enums.WebsocketEnum;
import com.itheima.mapper.NoticeMapper;
import com.itheima.mapper.UserLinkNoticeMapper;
import com.itheima.service.NoticeService;
import com.itheima.service.SensitiveWordFilterService;
import com.itheima.service.UserLinkNoticeService;
import com.itheima.service.UserService;
import com.itheima.utils.*;
import com.itheima.vo.NoticePersonVo;
import com.itheima.vo.NoticeVo;
import com.itheima.vo.WebsocketVo;
import com.itheima.vo.organization.OrganizationVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author 51585
 * @description 针对表【notice】的数据库操作Service实现
 * @createDate 2022-11-03 13:46:07
 */
@Service
@Slf4j
public class NoticeServiceImpl extends ServiceImpl<NoticeMapper, Notice>
        implements NoticeService {
    @Resource
    private UserService userService;
    @Resource
    private NoticeService noticeService;
    @Resource
    private UserLinkNoticeService userLinkNoticeService;
    @Resource
    private Websocket webSocket;
    @Resource
    private NoticeMapper noticeMapper;
    @Resource
    private OrganizationTreeUtil organizationTreeUtil;
    @Resource
    private NoticeConvert noticeConvert;
    @Resource
    private SensitiveWordFilterService sensitiveWordFilterService;
    @Resource
    private UserLinkNoticeMapper userLinkNoticeMapper;
    @Resource
    private SmsUtil smsUtil;

    @Override
    public void sendNotice(NoticeDto noticeDto) throws Exception {
        if (noticeDto.getStartTime().isAfter(noticeDto.getEndTime())) {
            throw new DefaultException("开始时间需在结束时间之前");
        }
        filterNoticeDto(noticeDto);
        if (noticeDto.getOperation().equals(NoticeEnum.All.getOperation())) {
            allOperation(noticeDto);
        } else {
            moreOperation(noticeDto);
        }
    }

    @Override
    public void acceptNotice(Integer noticeId) throws Exception {
        String userId = CheckUtil.checkLogin();
//        String userId = "19251104253";
        userLinkNoticeService.lambdaUpdate()
                .set(UserLinkNotice::getReadStatus, 1)
                .eq(UserLinkNotice::getNoticeId, noticeId)
                .eq(UserLinkNotice::getUserId, userId)
                .update();
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void deleteNotice(Integer noticeId) throws Exception {
        String userId = CheckUtil.checkLogin();
//        String userId = "19251104252";
        noticeIsExist(noticeId);
        if (!userService.isAdministratorsOrSchool(userId)) {
            Notice notice = this.lambdaQuery()
                    .select(Notice::getUserId)
                    .eq(Notice::getId, noticeId)
                    .one();
            if (!notice.getUserId().equals(userId)) {
                throw new DefaultException("对不起，你没有权限进行此操作");
            }
        }
        doDelete(noticeId);
        reNotification(noticeId, userId, NoticeStatusEnum.cancel.getNote());
    }

    /**
     * 把通知修改的信息重新通知下去
     * @param noticeId 通知id
     * @param userId 操作用户id
     * @param status 通知状态
     */
    public void reNotification(Integer noticeId, String userId, String status) {
        Notice notice = this.lambdaQuery()
                .eq(Notice::getId, noticeId)
                .one();
        NoticeDto noticeDto = noticeConvert.noticeToNoticeDto(notice, NoticeEnum.MORE.getOperation());
        List<UserLinkNotice> list = userLinkNoticeService.lambdaQuery()
                .select(UserLinkNotice::getNoticeId, UserLinkNotice::getUserId)
                .eq(UserLinkNotice::getNoticeId, noticeId)
                .list();
        Map<Integer, List<String>> collect = list.stream()
                .collect(Collectors.groupingBy(UserLinkNotice::getNoticeId,
                        Collectors.mapping(UserLinkNotice::getUserId, Collectors.toList())));
        noticeDto.setAcceptIds(collect.get(noticeId));
        reMoreOperation(noticeDto, userId, status);
    }

    /**
     * 使用websocket重新发通知
     * @param noticeDto 通知Dto
     * @param userId 操作用户id
     * @param status 通知状态
     */
    public void reMoreOperation(NoticeDto noticeDto, String userId, String status) {
        User user = userService.getById(userId);
        WebsocketVo websocketVo = convertNoticeDtoToWebsocketVo(noticeDto, user, status);
        moreWebsocket(websocketVo);
    }


    @Override
    public Paging<NoticeVo> pageNotice(PageNoticeDto pageNoticeDto) throws Exception {
        String userId = CheckUtil.checkLogin();
//        String userId = "19251104252";
        Page<?> page = new Page<>(pageNoticeDto.getPageIndex(), pageNoticeDto.getPageSize());
        IPage<NoticeVo> iPage = noticeMapper.pageNotice(page, pageNoticeDto, userId);
        if (!CollectionUtils.isEmpty(iPage.getRecords())) {
            supplyBriefUserVo(iPage.getRecords());
        }
        return PageUtils.toPage(iPage);

    }

    /**
     * 补充用户简略信息（自己发布的通知）
     *
     * @param list 通知vo
     */
    public void supplyBriefUserVo(List<NoticeVo> list) {
        List<Integer> noticeIdList = list.stream()
                .map(NoticeVo::getId)
                .collect(Collectors.toList());
        List<UserLinkNotice> list1 = userLinkNoticeService.lambdaQuery()
                .select(UserLinkNotice::getNoticeId, UserLinkNotice::getUserId)
                .in(UserLinkNotice::getNoticeId, noticeIdList)
                .list();
        Map<Integer, List<String>> map = list1.stream()
                .collect(Collectors.groupingBy(UserLinkNotice::getNoticeId, Collectors.mapping(UserLinkNotice::getUserId, Collectors.toList())));
        list.forEach(k -> {
            Integer noticeId = k.getId();
            if (!map.containsKey(noticeId)) {
                return;
            }
            k.setBriefUserVos(userService.getBriefUser(map.get(noticeId)));
        });
    }

    @Override
    public Paging<NoticePersonVo> pageNoticePerson(PageNoticeDto pageNoticeDto) throws Exception {
        String userId = CheckUtil.checkLogin();
//        String userId = "19251104252";
        Page<?> page = new Page<>(pageNoticeDto.getPageIndex(), pageNoticeDto.getPageSize());
        IPage<NoticePersonVo> iPage = noticeMapper.pageNoticePerson(page, pageNoticeDto, userId);
        List<String> userIdList = iPage.getRecords().stream().map(NoticePersonVo::getSendUserId).distinct().collect(Collectors.toList());
        if (!CollectionUtils.isEmpty(userIdList)) {
            List<User> list = userService.lambdaQuery()
                    .select(User::getId, User::getName, User::getImageUrl)
                    .in(User::getId, userIdList)
                    .list();
            Map<String, User> map = list.stream().collect(Collectors.toMap(User::getId, Function.identity()));
            iPage.getRecords().forEach(k -> {
                User user = map.get(k.getSendUserId());
                Optional.ofNullable(user).ifPresent(mid -> {
                    k.setSendUserName(mid.getName());
                    k.setSendUserImageUrl(mid.getImageUrl());
                });
            });
        }
        if (!CollectionUtils.isEmpty(iPage.getRecords())) {
            supplyPersonBriefUserVo(iPage.getRecords());
        }
        return PageUtils.toPage(iPage);
    }

    /**
     * 补充用户简略信息（需要自己处理的）
     *
     * @param list
     */
    public void supplyPersonBriefUserVo(List<NoticePersonVo> list) {
        List<Integer> noticeIdList = list.stream()
                .map(NoticePersonVo::getId)
                .collect(Collectors.toList());
        List<UserLinkNotice> list1 = userLinkNoticeService.lambdaQuery()
                .select(UserLinkNotice::getNoticeId, UserLinkNotice::getUserId)
                .in(UserLinkNotice::getNoticeId, noticeIdList)
                .list();
        Map<Integer, List<String>> map = list1.stream()
                .collect(Collectors.groupingBy(UserLinkNotice::getNoticeId, Collectors.mapping(UserLinkNotice::getUserId, Collectors.toList())));
        list.forEach(k -> {
            Integer noticeId = k.getId();
            if (!map.containsKey(noticeId)) {
                return;
            }
            k.setBriefUserVos(userService.getBriefUser(map.get(noticeId)));
        });
    }

    @Override
    public OrganizationVo showUser(Integer organizationId) throws DefaultException {
        return organizationTreeUtil.createByOrganization(organizationId);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void updateNotice(UpdateNoticeDto updateNoticeDto) throws Exception {
        String userId = CheckUtil.checkLogin();
//        String userId = "19251104252";
        filterUpdateNoticeDto(updateNoticeDto);
        Notice notice = noticeConvert.updateNoticeDtoToNotice(updateNoticeDto, userId);
        this.lambdaUpdate()
                .set(Notice::getTitle, notice.getTitle())
                .set(Notice::getContent, notice.getContent())
                .set(Notice::getStatus, NoticeStatusEnum.modify.getNote())
                .set(Notice::getStartTime, notice.getStartTime())
                .set(Notice::getEndTime, notice.getEndTime())
                .set(Notice::getUpdatedAt, LocalDateTime.now())
                .eq(Notice::getId, notice.getId())
                .update();
        List<String> acceptIds = updateNoticeDto.getAcceptIds();
        userLinkNoticeService.lambdaUpdate()
                .eq(UserLinkNotice::getNoticeId, updateNoticeDto.getId())
                .remove();
        List<UserLinkNotice> userLinkNoticeList = new ArrayList<>(acceptIds.size());
        acceptIds.forEach(k -> {
            UserLinkNotice userLinkNotice = new UserLinkNotice(null, updateNoticeDto.getId(), k, 0,LocalDateTime.now(),null,null);
            userLinkNoticeList.add(userLinkNotice);
        });
        userLinkNoticeService.saveBatch(userLinkNoticeList);
        reNotification(notice.getId(), userId, NoticeStatusEnum.modify.getNote());
    }

    @Override
    public void remindUser() throws Exception {
        List<String> list = userLinkNoticeMapper.unreadUsers();
        if (CollectionUtils.isEmpty(list)) {
            return;
        }
        List<User> userList = userService.lambdaQuery()
                .select(User::getId, User::getPhone)
                .in(User::getId, list)
                .isNull(User::getDeletedAt)
                .list();
        List<User> collect = userList.stream().distinct().collect(Collectors.toList());
        for (User user :
                collect) {
            smsUtil.sendNotice(user.getPhone());
            log.info(LocalDateTime.now() + "，已给:" + user.getId() + "用户发送通知");
        }

    }

    @Override
    public List<WebsocketVo> offlineNotice(String userId) {
        //涉及到的通知
        List<UserLinkNotice> userLinkNoticeList = userLinkNoticeService.lambdaQuery()
                .eq(UserLinkNotice::getUserId, userId)
                .eq(UserLinkNotice::getReadStatus, 0)
                .list();
        if (CollectionUtils.isEmpty(userLinkNoticeList)) {
            return null;
        }
        List<Integer> noticeIds = userLinkNoticeList.stream().map(UserLinkNotice::getNoticeId).distinct().collect(Collectors.toList());
        //收到通知的用户id
        Map<Integer, List<String>> collect = userLinkNoticeList.stream()
                .collect(Collectors.groupingBy(UserLinkNotice::getNoticeId, Collectors.mapping(UserLinkNotice::getUserId, Collectors.toList())));
        //通知具体信息
        List<Notice> noticeList = this.lambdaQuery()
                .in(Notice::getId, noticeIds)
                .orderByDesc(Notice::getStartTime)
                .list();
        List<String> userIds = noticeList.stream().map(Notice::getUserId).distinct().collect(Collectors.toList());
        //发送人信息
        List<User> userList = userService.lambdaQuery()
                .select(User::getName, User::getId, User::getImageUrl)
                .in(User::getId, userIds)
                .list();
        Map<String, User> userIdNameMap = userList.stream().collect(Collectors.toMap(User::getId, Function.identity()));
        List<WebsocketVo> res = noticeList.stream().map(notice -> {
            WebsocketVo websocketVo = new WebsocketVo();
            websocketVo.setSendUserId(notice.getUserId());
            Optional.ofNullable(userIdNameMap.get(notice.getUserId())).ifPresent(k -> {
                websocketVo.setSendUserName(k.getName());
                websocketVo.setSendUserImageUrl(k.getImageUrl());
            });
            websocketVo.setId(notice.getId());
            websocketVo.setTitle(notice.getTitle());
            websocketVo.setContent(notice.getContent());
            websocketVo.setAcceptIds(collect.get(notice.getId()));
            websocketVo.setSendTime(notice.getCreatedAt());
            websocketVo.setStartTime(notice.getStartTime());
            websocketVo.setEndTime(notice.getEndTime());
            websocketVo.setStatus(notice.getStatus());
            websocketVo.setType(WebsocketEnum.NOTICE.name());

            return websocketVo;
        }).collect(Collectors.toList());
        return res;
    }

    /**
     * 判断通知是否存在，不存在则抛出异常
     * @param noticeId 通知id
     * @throws Exception
     */
    public void noticeIsExist(Integer noticeId) throws Exception {
        Integer count = this.lambdaQuery()
                .select(Notice::getId)
                .eq(Notice::getId, noticeId)
                .count();
        if (count < 1) {
            throw new DefaultException("通知不存在");
        }
    }

    /**
     * 删除数据库通知信息
     * @param noticeId 通知id
     * @throws DefaultException
     */
    public void doDelete(Integer noticeId) throws DefaultException {
        boolean update = this.lambdaUpdate()
                .set(Notice::getStatus, NoticeStatusEnum.cancel.getNote())
                .set(Notice::getDeletedAt, LocalDateTime.now())
                .eq(Notice::getId, noticeId)
                .update();
        if (!update) {
            throw new DefaultException("删除失败");
        }
        updateNoticeReadStatus(noticeId);
    }

    /**
     * 把该通知设为未读
     *
     * @param noticeId 通知id
     */
    public void updateNoticeReadStatus(Integer noticeId) {
        userLinkNoticeService.lambdaUpdate()
                .set(UserLinkNotice::getReadStatus, 0)
                .eq(UserLinkNotice::getNoticeId, noticeId)
                .update();
    }

    /**
     * 广播通知操作
     *
     * @param noticeDto
     */
    public void allOperation(NoticeDto noticeDto) throws Exception {
        String userId = CheckUtil.checkLogin();
//        String userId = "19251104253";
        User user = userService.getById(userId);
        List<String> acceptIds;
        if (user.getOrganizationId().equals(OrganizationEnum.administrators.getOrganizationId())
                || user.getOrganizationId().equals(OrganizationEnum.school.getOrganizationId())) {
            acceptIds = userService.lambdaQuery()
                    .select(User::getId)
                    .isNull(User::getDeletedAt)
                    .list().stream().map(User::getId).collect(Collectors.toList());
        }else {
            ArrayList<Integer> childOrganizations = new ArrayList<>();
            ArrayList<Integer> bOrganizations = new ArrayList<>();
            bOrganizations.add(user.getOrganizationId());
            CheckUtil.getChild(childOrganizations, bOrganizations);
            acceptIds = getUserIdByOrganizationId(childOrganizations);
        }
        Notice notice = noticeConvert.noticeDtoToNotice(noticeDto, userId, LocalDateTime.now());
        saveNotice(notice, acceptIds);
        noticeDto.setAcceptIds(acceptIds);
        WebsocketVo websocketVo = convertNoticeDtoToWebsocketVo(noticeDto, user, NoticeStatusEnum.release.getNote());
        websocketVo.setId(notice.getId());
        allWebsocket(websocketVo);
    }

    /**
     * convertNoticeDto -> WebsocketVo
     * @param noticeDto 通知Dto
     * @param user 用户实体
     * @param status 状态
     * @return webSocket服务端到客户端vo
     */
    public WebsocketVo convertNoticeDtoToWebsocketVo(NoticeDto noticeDto, User user, String status) {
        WebsocketVo websocketVo = new WebsocketVo();
        websocketVo.setSendUserId(user.getId());
        websocketVo.setSendUserName(user.getName());
        websocketVo.setSendUserImageUrl(user.getImageUrl());
        websocketVo.setTitle(noticeDto.getTitle());
        websocketVo.setContent(noticeDto.getContent());
        websocketVo.setSendTime(LocalDateTime.now());
        websocketVo.setStartTime(noticeDto.getStartTime());
        websocketVo.setEndTime(noticeDto.getEndTime());
        websocketVo.setType(WebsocketEnum.NOTICE.name());
        websocketVo.setAcceptIds(noticeDto.getAcceptIds());
        websocketVo.setStatus(status);
        return websocketVo;
    }

    /**
     * 群发通知操作
     *
     * @param noticeDto 通知Dto
     */
    public void moreOperation(NoticeDto noticeDto) throws Exception {
        String userId = CheckUtil.checkLogin();
//        String userId = "19251104252";
        User user = userService.getById(userId);
        List<String> acceptIds = noticeDto.getAcceptIds().stream().distinct().collect(Collectors.toList());
        if (acceptIds.isEmpty()) {
            throw new DefaultException("没有选择被通知的对象");
        }
        acceptIds.remove(userId);
        List<User> acceptUsers = userService.lambdaQuery()
                .select(User::getOrganizationId)
                .in(User::getId, acceptIds)
                .list();
        List<Boolean> collect = acceptUsers.stream()
                .map(acceptUser -> CheckUtil.checkRole(user.getOrganizationId(), acceptUser.getOrganizationId()))
                .collect(Collectors.toList());
        if (collect.contains(false)) {
            throw new DefaultException("权限不足，无法发送通知");
        }
        Notice notice = noticeConvert.noticeDtoToNotice(noticeDto, userId, LocalDateTime.now());
        saveNotice(notice, acceptIds);
        WebsocketVo websocketVo = convertNoticeDtoToWebsocketVo(noticeDto, user, NoticeStatusEnum.release.getNote());
        websocketVo.setId(notice.getId());
        websocketVo.setAcceptIds(acceptIds);
        moreWebsocket(websocketVo);
    }

    /**
     * 保存通知关系到数据库
     * @param notice 通知实体
     * @param acceptIds 被通知的用户id
     */
    @Transactional(rollbackFor = Exception.class)
    public void saveNotice(Notice notice, List<String> acceptIds) {
        noticeService.save(notice);
        List<UserLinkNotice> collect = acceptIds.stream().map(acceptId -> {
            UserLinkNotice userLinkNotice = new UserLinkNotice();
            userLinkNotice.setNoticeId(notice.getId());
            userLinkNotice.setUserId(acceptId);
            return userLinkNotice;
        }).collect(Collectors.toList());
        userLinkNoticeService.saveBatch(collect);
    }

    /**
     * 根据organizationId获取userId
     *
     * @param organizationIds 组织id集合
     * @return 用户id集合
     */
    public List<String> getUserIdByOrganizationId(List<Integer> organizationIds) {
        List<User> users = userService.lambdaQuery()
                .select(User::getId)
                .in(User::getOrganizationId, organizationIds)
                .isNull(User::getDeletedAt)
                .list();
        List<String> acceptIds = users.stream()
                .map(User::getId)
                .collect(Collectors.toList());
        return acceptIds;
    }

    public void allWebsocket(WebsocketVo websocketVo) {
        webSocket.sendAllMessage(websocketVo);
    }

    public void moreWebsocket(WebsocketVo websocketVo) {
        webSocket.sendMoreMessage(websocketVo);
    }

    /**
     * 过滤NoticeDto中的敏感信息
     *
     * @param noticeDto 通知Dto
     */
    public void filterNoticeDto(NoticeDto noticeDto) throws JsonProcessingException {
        noticeDto.setTitle(sensitiveWordFilterService.filter(noticeDto.getTitle()));
        noticeDto.setContent(sensitiveWordFilterService.filter(noticeDto.getContent()));
    }

    /**
     * 过滤UpdateNoticeDto中的敏感信息
     *
     * @param updateNoticeDto 更新通知Dto
     */
    public void filterUpdateNoticeDto(UpdateNoticeDto updateNoticeDto) throws JsonProcessingException {
        updateNoticeDto.setTitle(sensitiveWordFilterService.filter(updateNoticeDto.getTitle()));
        updateNoticeDto.setContent(sensitiveWordFilterService.filter(updateNoticeDto.getContent()));
    }


}




